package com.fangyu.lottery.service;

import com.fangyu.lottery.exception.LotteryException;
import com.fangyu.lottery.model.Activity;
import com.fangyu.lottery.model.LotteryResult;
import com.fangyu.lottery.model.Prize;
import com.fangyu.lottery.model.PrizeDistributionMessage;
import com.fangyu.lottery.repository.ActivityRepository;
import com.fangyu.lottery.repository.PrizeRepository;
import com.fangyu.lottery.strategy.DrawStrategy;
import com.fangyu.lottery.strategy.DrawStrategyFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author fangyu
 * @version v1.0.0
 * @since 2025/8/27 23:38
 */
@Service
public class LotteryService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ActivityRepository activityRepository;
    @Autowired
    private PrizeRepository prizeRepository;

    // 获取活动类型
    private String getActivityType(Long activityId) {
        // 先从Redis缓存中获取
        String cacheKey = "activity:" + activityId + ":type";
        String activityType = (String) redisTemplate.opsForValue().get(cacheKey);

        if (activityType != null) {
            return activityType;
        }

        // 缓存中没有，从数据库查询
        Activity activity = activityRepository.findById(activityId)
                .orElseThrow(() -> new LotteryException("活动不存在", "ACTIVITY_NOT_FOUND"));

        activityType = activity.getType();

        // 将结果存入缓存，设置过期时间
        redisTemplate.opsForValue().set(cacheKey, activityType, 1, TimeUnit.HOURS);

        return activityType;
    }

    // 抽奖核心方法
    public LotteryResult draw(Long activityId, Long userId) {
        // 1. 资格校验
        if (!checkQualification(activityId, userId)) {
            throw new LotteryException("您没有参与资格", "QUALIFICATION_FAILED");
        }

        // 2. 执行抽奖算法
        Prize prize = drawAlgorithm(activityId);
        if (prize == null) {
            throw new LotteryException("很遗憾未中奖", "DRAW_FAILED");
        }

        // 3. 原子性扣减库存
        boolean deductSuccess = deductStock(activityId, prize.getId());
        if (!deductSuccess) {
            throw new LotteryException("奖品已发完", "STOCK_NOT_ENOUGH");
        }

        // 4. 发放奖品
        distributePrize(activityId, userId, prize);

        // 5. 记录用户参与次数
        recordUserParticipation(activityId, userId);

        return new LotteryResult(prize, new Date());
    }

    // 资格校验
    private boolean checkQualification(Long activityId, Long userId) {
        String key = "activity:" + activityId + ":user:" + userId + ":qualified";
        Boolean qualified = (Boolean) redisTemplate.opsForValue().get(key);
        if (qualified != null && qualified) {
            return true;
        }

        // 更复杂的校验逻辑，如用户等级、黑名单等
        // ...

        return true; // 简化处理，实际项目中应有完整校验
    }

    // 抽奖算法 - 策略模式示例
    private Prize drawAlgorithm(Long activityId) {
        // 获取活动奖品配置
        List<Prize> prizes = getActivityPrizes(activityId);
        if (prizes.isEmpty()) {
            return null;
        }

        // 根据不同的活动类型使用不同的抽奖策略
        String activityType = getActivityType(activityId);
        DrawStrategy strategy = DrawStrategyFactory.getStrategy(activityType);
        return strategy.draw(prizes);
    }

    // 原子性扣减库存 - 使用Lua脚本
    private boolean deductStock(Long activityId, Long prizeId) {
        String script =
                "local stockKey = KEYS[1] " +
                        "local stock = redis.call('hget', stockKey, ARGV[1]) " +
                        "if stock and tonumber(stock) > 0 then " +
                        "   redis.call('hincrby', stockKey, ARGV[1], -1) " +
                        "   return 1 " +
                        "else " +
                        "   return 0 " +
                        "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);

        String stockKey = "activity:" + activityId + ":stock";
        Long result = redisTemplate.execute(redisScript,
                Collections.singletonList(stockKey),
                prizeId.toString());

        return result != null && result == 1;
    }

    // 发放奖品 - 使用MQ异步处理
    private void distributePrize(Long activityId, Long userId, Prize prize) {
        PrizeDistributionMessage message = new PrizeDistributionMessage();
        message.setActivityId(activityId);
        message.setUserId(userId);
        message.setPrizeId(prize.getId());
        message.setPrizeType(prize.getType());
        message.setDistributeTime(new Date());

        // 发送到MQ，异步处理
        rabbitTemplate.convertAndSend("prize.exchange", "prize.distribution", message);
    }

    // 记录用户参与
    private void recordUserParticipation(Long activityId, Long userId) {
        String key = "activity:" + activityId + ":user:" + userId + ":participated";
        redisTemplate.opsForValue().set(key, true, 24, TimeUnit.HOURS); // 24小时内不能重复参与

        // 记录用户参与次数
        String countKey = "activity:" + activityId + ":user:" + userId + ":count";
        redisTemplate.opsForValue().increment(countKey);
    }

    // 获取活动奖品列表
    private List<Prize> getActivityPrizes(Long activityId) {
        // 先从Redis缓存中获取
        String cacheKey = "activity:" + activityId + ":prizes";
        List<Prize> prizes = (List<Prize>) redisTemplate.opsForValue().get(cacheKey);

        if (prizes != null && !prizes.isEmpty()) {
            return prizes;
        }

        // 缓存中没有，从数据库查询
        prizes = prizeRepository.findByActivityId(activityId);

        if (prizes.isEmpty()) {
            throw new LotteryException("活动未配置奖品", "NO_PRIZES_CONFIGURED");
        }

        // 将结果存入缓存，设置过期时间
        redisTemplate.opsForValue().set(cacheKey, prizes, 30, TimeUnit.MINUTES);

        return prizes;
    }
}